﻿using System;
using System.Collections.Generic;
using System.Xml;
using CID.Domain.Exceptions;
using CID.Domain.Operations;
using NailsFramework.Persistence;

namespace CID.Domain.Infrastructure
{
    public abstract class ParameterValue : Model<ParameterValue>, IParameterValue
    {
        public virtual int Id { get; private set; }
        public virtual IParameter Parameter { get; private set; }
        public virtual IDefinition Definition { get; private set; }

        protected ParameterValue()
        { 
        }

        internal ParameterValue(IDefinition definition, IParameter parameter) : this()
        {
            Definition = definition;
            Parameter = parameter;
        }

        #region ParameterValue handling
        public virtual string SerializedValue { get; private set; }

        public virtual string StringValue
        {
            get { return DeserializeAsString(); }
            set { Serialize(value); }
        }

        public virtual int IntegerValue
        {
            get { return DeserializeAsInteger(); }
            set { Serialize(value); }
        }

        public virtual decimal DecimalValue
        {
            get { return DeserializeAsDecimal(); }
            set { Serialize(value); }
        }

        public virtual DateTime DateTimeValue
        {
            get { return DeserializeAsDateTime(); }
            set { Serialize(value); }
        }

        public virtual bool BooleanValue
        {
            get { return DeserializeAsBoolean(); }
            set { Serialize(value); }
        }

        public virtual Guid UniqueIdentifierValue
        {
            get { return DeserializeAsUniqueIdentifier(); }
            set { Serialize(value); }
        }

        public virtual XmlDocument XmlValue
        {
            get { return DeserializeAsXml(); }
            set { Serialize(value); }
        }

        private void CheckParameterType(ParameterType requiredType)
        {
            if (Parameter.Type != requiredType) throw new InvalidParameterValueTypeException(Parameter.Type, requiredType);
        }

        private void Serialize(string value)
        {
            CheckParameterType(ParameterType.String);
            SerializedValue = ParameterValueSerializer.Serialize(value);
        }

        private void Serialize(int value)
        {
            CheckParameterType(ParameterType.Integer);
            SerializedValue = ParameterValueSerializer.Serialize(value);
        }

        private void Serialize(decimal value)
        {
            CheckParameterType(ParameterType.Decimal);
            SerializedValue = ParameterValueSerializer.Serialize(value);
        }

        private void Serialize(DateTime value)
        {
            CheckParameterType(ParameterType.DateTime);
            SerializedValue = ParameterValueSerializer.Serialize(value);
        }

        private void Serialize(bool value)
        {
            CheckParameterType(ParameterType.Boolean);
            SerializedValue = ParameterValueSerializer.Serialize(value);
        }

        private void Serialize(Guid value)
        {
            CheckParameterType(ParameterType.UniqueIdentifier);
            SerializedValue = ParameterValueSerializer.Serialize(value);
        }

        private void Serialize(XmlDocument value)
        {
            CheckParameterType(ParameterType.Xml);
            SerializedValue = ParameterValueSerializer.Serialize(value);
        }

        private string DeserializeAsString()
        {
            CheckParameterType(ParameterType.String);
            return ParameterValueSerializer.DeserializeAsString(SerializedValue);
        }

        private int DeserializeAsInteger()
        {
            CheckParameterType(ParameterType.Integer);
            return ParameterValueSerializer.DeserializeAsInteger(SerializedValue);
        }

        private decimal DeserializeAsDecimal()
        {
            CheckParameterType(ParameterType.Decimal);
            return ParameterValueSerializer.DeserializeAsDecimal(SerializedValue);
        }

        private DateTime DeserializeAsDateTime()
        {
            CheckParameterType(ParameterType.DateTime);
            return ParameterValueSerializer.DeserializeAsDateTime(SerializedValue);
        }

        private bool DeserializeAsBoolean()
        {
            CheckParameterType(ParameterType.Boolean);
            return ParameterValueSerializer.DeserializeAsBoolean(SerializedValue);
        }

        private Guid DeserializeAsUniqueIdentifier()
        {
            CheckParameterType(ParameterType.UniqueIdentifier);
            return ParameterValueSerializer.DeserializeAsUniqueIdentifier(SerializedValue);
        }

        private XmlDocument DeserializeAsXml()
        {
            CheckParameterType(ParameterType.Xml);
            return ParameterValueSerializer.DeserializeAsXml(SerializedValue);
        }
        #endregion

        public virtual void CopyTo(IParameterValue target)
        {
            if (!Parameter.Equals(target.Parameter)) throw new InvalidActionException("Unable to copy the parameter value as the target belongs to a different parameter");

            switch (Parameter.Type)
            {
                case ParameterType.String: target.StringValue = StringValue; break;
                case ParameterType.Integer: target.IntegerValue = IntegerValue; break;
                case ParameterType.Decimal: target.DecimalValue = DecimalValue; break;
                case ParameterType.Boolean: target.BooleanValue = BooleanValue; break;
                case ParameterType.DateTime: target.DateTimeValue = DateTimeValue; break;
                case ParameterType.UniqueIdentifier: target.UniqueIdentifierValue = UniqueIdentifierValue; break;
                case ParameterType.Xml: 
                    var document = new XmlDocument();
                    document.LoadXml(XmlValue.OuterXml);
                    target.XmlValue = document;
                    break;
            }
        }
    }

    public class DefaultParameterValue : ParameterValue, IDefaultParameterValue
    {
        #region Setting Default Values
        private static readonly Dictionary<ParameterType, Action<DefaultParameterValue>> SetDefaultValueMethodsMap = new Dictionary<ParameterType, Action<DefaultParameterValue>>();

        private static void SetDefaultStringValue(DefaultParameterValue parameterValue)
        {
            parameterValue.StringValue = String.Empty;
        }

        private static void SetDefaultIntegerValue(DefaultParameterValue parameterValue)
        {
            parameterValue.IntegerValue = default(int);
        }

        private static void SetDefaultDecimalValue(DefaultParameterValue parameterValue)
        {
            parameterValue.DecimalValue = default(decimal);
        }

        private static void SetDefaultDateTimeValue(DefaultParameterValue parameterValue)
        {
            parameterValue.DateTimeValue = default(DateTime);
        }

        private static void SetDefaultBooleanValue(DefaultParameterValue parameterValue)
        {
            parameterValue.BooleanValue = default(bool);
        }

        private static void SetDefaultUniqueIdentifierValue(DefaultParameterValue parameterValue)
        {
            parameterValue.UniqueIdentifierValue = default(Guid);
        }

        private static void SetDefaultXmlValue(DefaultParameterValue parameterValue)
        {
            XmlDocument value = new XmlDocument();
            value.LoadXml("<?xml version=\"1.0\"?><root></root>");
            parameterValue.XmlValue = value;
        }

        static DefaultParameterValue()
        {
            SetDefaultValueMethodsMap.Add(ParameterType.String, SetDefaultStringValue);
            SetDefaultValueMethodsMap.Add(ParameterType.Integer, SetDefaultIntegerValue);
            SetDefaultValueMethodsMap.Add(ParameterType.Decimal, SetDefaultDecimalValue);
            SetDefaultValueMethodsMap.Add(ParameterType.DateTime, SetDefaultDateTimeValue);
            SetDefaultValueMethodsMap.Add(ParameterType.Boolean, SetDefaultBooleanValue);
            SetDefaultValueMethodsMap.Add(ParameterType.UniqueIdentifier, SetDefaultUniqueIdentifierValue);
            SetDefaultValueMethodsMap.Add(ParameterType.Xml, SetDefaultXmlValue);
        }
        #endregion

        protected DefaultParameterValue() : base() { }

        internal DefaultParameterValue(IDefinition definition, IParameter parameter)
            : base(definition, parameter)
        {
            SetDefaultValueMethodsMap[parameter.Type](this);
        }
    }

    public class EnvironmentParameterValue : ParameterValue, IEnvironmentParameterValue
    {
        protected EnvironmentParameterValue() : base() { }

        public virtual IEnvironment Environment { get; private set; }

        internal EnvironmentParameterValue(IDefinition definition, IParameter parameter, IEnvironment environment)
            : base(definition, parameter)
        {
            Environment = environment;
        }
    }
}